package com.carey.blog.action.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;

import com.carey.blog.action.util.Filler;
import com.carey.blog.framework.latke.Keys;
import com.carey.blog.framework.latke.action.AbstractCacheablePageAction;
import com.carey.blog.framework.latke.action.ActionException;
import com.carey.blog.framework.latke.model.User;
import com.carey.blog.framework.latke.repository.RepositoryException;
import com.carey.blog.framework.latke.service.LangPropsService;
import com.carey.blog.framework.latke.util.CollectionUtils;
import com.carey.blog.framework.latke.util.Locales;
import com.carey.blog.framework.latke.util.Strings;
import com.carey.blog.model.Article;
import com.carey.blog.model.Common;
import com.carey.blog.model.Preference;
import com.carey.blog.repository.ArticleRepository;
import com.carey.blog.repository.TagArticleRepository;
import com.carey.blog.repository.TagRepository;
import com.carey.blog.repository.impl.ArticleGAERepository;
import com.carey.blog.repository.impl.TagArticleGAERepository;
import com.carey.blog.repository.impl.TagGAERepository;
import com.carey.blog.util.Articles;
import com.carey.blog.util.Preferences;
import com.carey.blog.util.Statistics;
import com.carey.blog.util.comparator.Comparators;

/**
 * Article action. <article-detail.ftl>.
 * 
 */
public final class ArticleAction extends AbstractCacheablePageAction {

	/**
	 * Default serial version uid.
	 */
	private static final long serialVersionUID = 1L;
	/**
	 * Logger.
	 */
	private static final Logger LOGGER = Logger.getLogger(ArticleAction.class
			.getName());
	/**
	 * Article repository.
	 */
	private ArticleRepository articleRepository = ArticleGAERepository
			.getInstance();
	/**
	 * Tag-Article repository.
	 */
	private TagArticleRepository tagArticleRepository = TagArticleGAERepository
			.getInstance();
	/**
	 * Tag repository.
	 */
	private TagRepository tagRepository = TagGAERepository.getInstance();
	/**
	 * Filler.
	 */
	private Filler filler = Filler.getInstance();
	/**
	 * Language service.
	 */
	private LangPropsService langPropsService = LangPropsService.getInstance();
	/**
	 * Article utilities.
	 */
	private Articles articleUtils = Articles.getInstance();
	/**
	 * Preference utilities.
	 */
	private Preferences preferenceUtils = Preferences.getInstance();
	/**
	 * Statistic utilities.
	 */
	private Statistics statistics = Statistics.getInstance();

	@Override
	protected Map<?, ?> doFreeMarkerAction(
			final freemarker.template.Template template,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException {
		request.setAttribute(CACHED_TYPE, Article.ARTICLE);
		final Map<String, Object> ret = new HashMap<String, Object>();

		try {
			final JSONObject preference = preferenceUtils.getPreference();
			if (null == preference) {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);
				return ret;
			}

			final String localeString = preference
					.getString(Preference.LOCALE_STRING);
			final Locale locale = new Locale(Locales.getLanguage(localeString),
					Locales.getCountry(localeString));

			final Map<String, String> langs = langPropsService.getAll(locale);
			ret.putAll(langs);

			final JSONObject queryStringJSONObject = getQueryStringJSONObject(request);
			String articleId = queryStringJSONObject.optString(Keys.OBJECT_ID);
			if (Strings.isEmptyOrNull(articleId)) {
				articleId = (String) request.getAttribute(Keys.OBJECT_ID);
			}

			if (Strings.isEmptyOrNull(articleId)) {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);
				return ret;
			}

			final JSONObject article = articleRepository.get(articleId);
			if (null == article
					|| !article.getBoolean(Article.ARTICLE_IS_PUBLISHED)) {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);
				return ret;
			}

			request.setAttribute(CACHED_OID, articleId);
			statistics.incArticleViewCount(articleId);

			LOGGER.log(Level.FINEST, "Article[title={0}]",
					article.getString(Article.ARTICLE_TITLE));
			ret.put(Article.ARTICLE, article);

			// For <meta name="description"
			// content="${article.articleAbstract}"/>
			final String metaDescription = Jsoup.parse(
					article.getString(Article.ARTICLE_ABSTRACT)).text();
			article.put(Article.ARTICLE_ABSTRACT, metaDescription);

			if (preference.getBoolean(Preference.ENABLE_ARTICLE_UPDATE_HINT)) {
				article.put(Common.HAS_UPDATED,
						articleUtils.hasUpdated(article));
			} else {
				article.put(Common.HAS_UPDATED, false);
			}

			final JSONObject author = articleUtils.getAuthor(article);
			final String authorName = author.getString(User.USER_NAME);
			article.put(Common.AUTHOR_NAME, authorName);
			
			final String authorId = author.getString(Keys.OBJECT_ID);
			article.put(Common.AUTHOR_ID, authorId);
			article.put(Common.AUTHOR_ROLE, author.getString(User.USER_ROLE));

			LOGGER.finer("Getting article sign....");
			article.put(Article.ARTICLE_SIGN_REF,
					articleUtils.getSign(articleId, preference));
			LOGGER.finer("Got article sign finished!");

			LOGGER.finer("Getting the previous article....");
			final JSONObject previousArticle = articleRepository
					.getPreviousArticle(articleId);
			if (null != previousArticle) {
				ret.put(Common.PREVIOUS_ARTICLE_PERMALINK,
						previousArticle.getString(Article.ARTICLE_PERMALINK));
				ret.put(Common.PREVIOUS_ARTICLE_TITLE,
						previousArticle.getString(Article.ARTICLE_TITLE));
				LOGGER.finer("Got the previous article finished!");
			}

			LOGGER.finer("Getting the next article....");
			final JSONObject nextArticle = articleRepository
					.getNextArticle(articleId);
			if (null != nextArticle) {
				ret.put(Common.NEXT_ARTICLE_PERMALINK,
						nextArticle.getString(Article.ARTICLE_PERMALINK));
				ret.put(Common.NEXT_ARTICLE_TITLE,
						nextArticle.getString(Article.ARTICLE_TITLE));
				LOGGER.finer("Got the next article finished!");
			}

			LOGGER.finer("Getting article's comments....");
			final List<JSONObject> articleComments = articleUtils
					.getComments(articleId);
			ret.put(Article.ARTICLE_COMMENTS_REF, articleComments);
			LOGGER.finer("Got article's comments finished!");

			LOGGER.finer("Getting relevant articles....");
			final List<JSONObject> relevantArticles = getRelevantArticles(
					articleId, article.getString(Article.ARTICLE_TAGS_REF),
					preference);
			ret.put(Common.RELEVANT_ARTICLES, relevantArticles);
			LOGGER.finer("Got relevant articles finished!");

			ret.put(Preference.EXTERNAL_RELEVANT_ARTICLES_DISPLAY_CNT,
					preference
							.getInt(Preference.EXTERNAL_RELEVANT_ARTICLES_DISPLAY_CNT));

			filler.fillSide(ret, preference);
			filler.fillBlogHeader(ret, preference);
			filler.fillBlogFooter(ret, preference);
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);

			try {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);

				return ret;
			} catch (final IOException ex) {
				LOGGER.severe(ex.getMessage());
			}
		}

		return ret;
	}

	/**
	 * Gets the relevant published articles by the specified article tags string
	 * excludes the specified article id.
	 * 
	 * @param articleId
	 *            the specified article id
	 * @param articleTagsString
	 *            the specified article tags string
	 * @param preference
	 *            the specified preference
	 * @return a list of articles, returns an empty list if not found
	 * @throws RepositoryException
	 *             repository exception
	 * @throws JSONException
	 *             json exception
	 */
	private List<JSONObject> getRelevantArticles(final String articleId,
			final String articleTagsString, final JSONObject preference)
			throws JSONException, RepositoryException {
		if (null == preference) {
			throw new RepositoryException("Not found preference");
		}

		final int displayCnt = preference
				.getInt(Preference.RELEVANT_ARTICLES_DISPLAY_CNT);
		final String[] tagTitles = articleTagsString.split(",");
		final int maxTagCnt = displayCnt > tagTitles.length ? tagTitles.length
				: displayCnt;
		final List<JSONObject> articles = new ArrayList<JSONObject>();
		for (int i = 0; i < maxTagCnt; i++) { // XXX: should average by tag?
			final String tagTitle = tagTitles[i];
			final JSONObject tag = tagRepository.getByTitle(tagTitle);
			final String tagId = tag.getString(Keys.OBJECT_ID);
			final JSONObject result = tagArticleRepository.getByTagId(tagId, 1,
					displayCnt);
			final JSONArray tagArticleRelations = result
					.getJSONArray(Keys.RESULTS);

			final int relationSize = displayCnt < tagArticleRelations.length() ? displayCnt
					: tagArticleRelations.length();
			for (int j = 0; j < relationSize; j++) {
				final JSONObject tagArticleRelation = tagArticleRelations
						.getJSONObject(j);
				final String relatedArticleId = tagArticleRelation
						.getString(Article.ARTICLE + "_" + Keys.OBJECT_ID);
				if (articleId.equals(relatedArticleId)) {
					continue;
				}

				if (articleRepository.isPublished(relatedArticleId)) {
					final JSONObject article = articleRepository
							.get(relatedArticleId);

					boolean existed = false;
					for (final JSONObject relevantArticle : articles) {
						if (relevantArticle.getString(Keys.OBJECT_ID).equals(
								article.getString(Keys.OBJECT_ID))) {
							existed = true;
						}
					}

					if (!existed) {
						articles.add(article);
					}
				}
			}
		}

		Collections.sort(articles, Comparators.ARTICLE_UPDATE_DATE_COMPARATOR);

		if (displayCnt > articles.size()) {
			return articles;
		}

		final List<Integer> randomIntegers = CollectionUtils.getRandomIntegers(
				0, articles.size() - 1, displayCnt);
		final List<JSONObject> ret = new ArrayList<JSONObject>();
		for (final int index : randomIntegers) {
			ret.add(articles.get(index));
		}

		return ret;
	}

	@Override
	protected JSONObject doAjaxAction(final JSONObject data,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException {
		throw new UnsupportedOperationException("Not supported yet.");
	}
}
